{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第一题"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "32\n",
      "32\n",
      "[ 4 10 18]\n",
      "[ 4 10 18]\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "from numpy import inner\n",
    "x=np.array([1,2,3])\n",
    "y=np.array([4,5,6])\n",
    "\n",
    "#内积\n",
    "inner_product=inner(x,y)\n",
    "print(inner_product)\n",
    "np.dot(x,y)\n",
    "print(np.dot(x,y))\n",
    "\n",
    "#对应元素相乘\n",
    "np.multiply(x,y)\n",
    "print(np.multiply(x,y))\n",
    "z=x*y\n",
    "print(z)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 第二题"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import scipy.spatial.distance as ssd\n",
    "from collections import defaultdict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#读取训练集，使用到的列为user_id，item_id，rating\n",
    "df_training_data = pd.read_csv(\n",
    "    './movielen_rating_training.base',\n",
    "    names=['user_id','item_id','rating'],\n",
    "    usecols=[0,1,2],\n",
    "    sep='\\t')\n",
    "df_training_data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#不重复的user_id与item_id列表\n",
    "user_id_s=df_training_data['user_id'].unique()\n",
    "item_id_s=df_training_data['item_id'].unique()\n",
    "#建立id与index的索引\n",
    "#例如：user_id_map[【user_id】]=user_index\n",
    "user_index_map={}\n",
    "item_index_map={}\n",
    "for user_index in range(len(user_id_s)):\n",
    "    user_id=user_id_s[user_index]\n",
    "    user_index_map[user_id]=user_index\n",
    "for item_index in range(len(item_index)):\n",
    "    item_id=item_id_s[item_index]\n",
    "    item_index_map[item_id]=item_index"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#用户与物品的打分矩阵\n",
    "user_item_rating_array = np.zeros(shape=(len(user_id_s),len(item_id_s)))\n",
    "#用户打分商品的索引集合\n",
    "user_rating_map=defaultdict(set)\n",
    "\n",
    "for row_index in df_training_data.index:\n",
    "    #每一行的数据\n",
    "    row_data=df_training_data.iloc[row_index]\n",
    "    #打分用户的索引\n",
    "    user_index=user_index_map[row_data['user_id']]\n",
    "    #打分电影的索引\n",
    "    item_index=item_index_map[row_data['item_id']]\n",
    "    #添加用户打分商品索引集合\n",
    "    user_rating_map[user_index].add(item_index)\n",
    "    #矩阵中行=user_index，列=item_index的元素赋值为打分\n",
    "    user_item_rating_array[user_index,item_index]=row_data['rating']\n",
    "user_item_rating_array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#计算用户的平均打分向量\n",
    "def calculate_user_rating_mu():\n",
    "    user_rating_mu_s=[]\n",
    "    for user_index in range(len(user_id_s)):\n",
    "        #计算打过分的电影评分向量\n",
    "        item_rating_index_v=list(user_rating_map[user_index])\n",
    "        #打过分的电影评分向量\n",
    "        item_rating_v = np.take(\n",
    "            user_item_rating_array[user_index],\n",
    "            item_rating_index_v\n",
    "        )\n",
    "        #打分向量的平均值\n",
    "        mu=item_rating_v.mean()\n",
    "        #保留两位小数\n",
    "        mu=round(mu,2)\n",
    "        user_rating_mu_s.append(mu)\n",
    "    return user_rating_mu_s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义用户相似度函数\n",
    "def calculate_sim(user_index1,user_index2):\n",
    "    #取用户1和用户2公共打分的电影集合，转换成列表\n",
    "    intersection_index_s=list(\n",
    "        user_rating_map[user_index]&user_rating_ma[user_index2]\n",
    "    )\n",
    "    #如果没有公共的打分项，相似度为0.0\n",
    "    if not intersection_index_s:\n",
    "        return 0.0\n",
    "    #根据公共索引，取到用户1的打分向量，并为均值\n",
    "    v1=np.take(\n",
    "        user_item_rating_array[user_index1],\n",
    "        intersection_index_s\n",
    "    )-user_rating_mu_s[user_index1]\n",
    "    #根据公共索引，取到用户2的打分向量，并去均值\n",
    "    v2=np.take(\n",
    "                user_item_rating_array[user_index2],\n",
    "        intersection_index_s\n",
    "    )-user_rating_mu_s[user_index2]\n",
    "    #计算相似度\n",
    "    sim=1-ssd.cosine(v1,v2)\n",
    "    #如果相似度不是数字（如果v1或v2是0向量），返回相似度0\n",
    "    if np.isnan(sim):\n",
    "        return 0.0\n",
    "    #否则相似度保留两位小数，返回结构\n",
    "    else:\n",
    "        return round(sim,2)\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "user_similarity_array=np.zeros(shape=(len(user_id_s),len(user_id_s)))\n",
    "for user_index1 in range(len(user_d_s)):\n",
    "    print('计算到用户：%s与其余用户的相似度'% user_index1)\n",
    "    for user_index2 in range(user_index1+1,len(user_id_s)):\n",
    "        #计算用户1和用户2的相似度\n",
    "        sim = calculate_sim(user_index1,user_index2)\n",
    "        #用户1与用户2的相似度=sim\n",
    "        user_similarity_array[user_index1,user_index2] = sim\n",
    "        #用户2与用户1的相似度=sim\n",
    "        user_similarity_array[user_index2,user_index1] = sim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "user_similarity_array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#用户对商品的预测矩阵，已打分的商品，预测分数为0分\n",
    "user_item_predict_rating_array = np.zeros_like(user_item_rating_array)\n",
    "\n",
    "#对所有的用户index进行遍历\n",
    "for user_index_in range(len(user_id_s)):\n",
    "    print('预测到用户%s'%user_index)\n",
    "    #对所有商品进行遍历\n",
    "    for item_index in range(len(item_id_s)):\n",
    "        #如果这个商品没有被打过分\n",
    "        if item_index not in user_rating_map[user_index]:\n",
    "            #找到对这个商品打过分的所有用户的索引\n",
    "            user_rating_index_v=np.where(\n",
    "                user_item_rating_array[:,item_index]>0\n",
    "            )[0]\n",
    "            #如果没有用户对这个商品打过分，continue\n",
    "            if not list(user_rating_index_v):\n",
    "                continue\n",
    "            #根据用户打分的索引，从用户相似度矩阵中取出相似度向量\n",
    "            user_sim_v = np.take(\n",
    "                user_similarity_array[user_index],\n",
    "                user_rating_index_v,\n",
    "            )\n",
    "            #计算相似度绝对值加和\n",
    "            user_sim_abs_sum = user_sim_v._abs_().sum()\n",
    "            #如果相似度绝对值加和为0（也就是存在对这个商品打过分的用户群体，但这些用户群体与目标用户的相关度都为0），continue\n",
    "            if user_sim_abs_sum==0\n",
    "                continue\n",
    "            #得到用户打分的向量，并去掉平均值\n",
    "            user_rating_v=np.take(\n",
    "                user_item_rating_array[:,item_index],\n",
    "                user_rating_index_v\n",
    "            )-np.take(\n",
    "                user_rating_mu_s,\n",
    "                user_rating_index_v\n",
    "            )\n",
    "            #根据公式，得到预测的结果，这里保留两位小数\n",
    "            predict_rating=round(\n",
    "                (np.dot(user_rating_v,user_sima_v) + user_rating_mu_s[user_index])/user_sim_abs_sum\n",
    "                ,2\n",
    "            ) + user_rating_mu_s[user_index]\n",
    "            #把预测的结果添加到预测矩阵中\n",
    "            user_item_predict_rating_array[user_index,item_index] = predict_rating\n",
    "#打印预测矩阵\n",
    "user_item_predict_rating_array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#读取测试集\n",
    "df_test_data = pd.read_csv('./movielen_rating_test.base',sep='\\t',names=['user_id','item_id','rating'].usecols=[0,1,2])\n",
    "de_test_data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#测试集中不重复的用户id\n",
    "user_test_unique_s = df_test_data['user_id'].unique()\n",
    "#创建一个列表，保存测试集中的user_id对应的user_index\n",
    "user_index_test_s=[]\n",
    "#对测试集中的用户id进行遍历\n",
    "for user_id in user_test_unique_s:\n",
    "        #如果测试集中的用户id在训练集的用户索引map中，添加这个user_index\n",
    "        if user_id in user_index_map.keys():\n",
    "            user_index_test_s.append(user_index_map[user_id])\n",
    "#打印测试集中的user_index列表\n",
    "user_index_test_s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#创建一个用户对商品打分的dataframe\n",
    "df_user_item_rating_test = pd.DataFrame(np.zeros(shape=(len(user_index_test_s),len(item_id_s))))\n",
    "#dataframe的index设置为在训练集中存在的测试集的user_index\n",
    "df_user_item_rating_test.index=user_index_test_s\n",
    "#对dataframe的index进行遍历\n",
    "for row_index in df_test_data.index:\n",
    "    print('运行到%s行'%row_index)\n",
    "    row_data = df_test_data.loc[row_index]\n",
    "    #如果这个用户id在user_rating_map中，商品id也在item_index_map中，添加这个打分\n",
    "    if row_data['user_id'] in user_rating_map.keys() and row_data['item_id'] in item_index_map.keys():\n",
    "        df_user_item_rating_test[item_index_map[row_data['item_id']]][user_index_map[row_data['user_id']]]=row_data['rating']\n",
    "#打印dataframe\n",
    "df_user_item_rating_test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def calculate_RMSE():\n",
    "    #acc_locc为分子，二次损失\n",
    "    acc_loss = 0\n",
    "    #acc_num为分母，一共计算了多少项\n",
    "    acc_num=0\n",
    "    #对测试集的index进行遍历\n",
    "    for user_index in df_user_item_rating_test.index:\n",
    "        #测试集中user_index对应的行向量\n",
    "        test_row_data = np.array(df_user_item_rating_test.loc[user_index])\n",
    "        #预测矩阵中的行向量\n",
    "        predict_row_data = user_item_predict_rating_array[user_index]\n",
    "        #测试集中对应user_index，打过分的商品索引\n",
    "        test_index_v = np.where(test_row_data>0)\n",
    "        #预测矩阵中对应user_index，打过分的商品索引\n",
    "        predict_index_v = np.where(predict_row_data>0)\n",
    "        #取test_index_v和predict_index_v的交集，即预测过打分，而且也在测试集中出现实际打分\n",
    "        intersection_index_s = list(\n",
    "            set(test_index_v[0]) & set(predict_index_v[0])\n",
    "        )\n",
    "        #如果交集为空，continue\n",
    "        if not intersection_index_s:\n",
    "            continue\n",
    "        #根据上述的交集索引，取得测试集中的打分向量和预测矩阵中的打分向量\n",
    "        test_rating_v = np.take(test_row_data.interection_index_s)\n",
    "        predict_rating_v = np.clip(\n",
    "            np.take(predict_row_data,intersection_index_s),0,5\n",
    "        )\n",
    "        #计算二次损失\n",
    "        acc_loss += np.square(test_rating_v-predict_rating_v).sum()\n",
    "        #分母叠加个数\n",
    "        acc_num += len(intersection_index_s)\n",
    "#得出均方误差\n",
    "RMSE = calculate_RMSE()\n",
    "RMSE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#推荐商品，predict_quantity是推荐的商品的个数\n",
    "def predict(predict_quantity):\n",
    "    #建立一个商品推荐字典，保存对user_index推荐的商品索引\n",
    "    #例如：predict_item_index_map[user_index] = [4,3,2,5]\n",
    "    predict_item_index_map={}\n",
    "    #对训练集中所有的user_index进行遍历\n",
    "    for user_inded in range(len(user_id_s)):\n",
    "        #预测矩阵中对应user_index的向量，进行倒序排列\n",
    "        predict_item_index_v= list(np.argsort(-user_item_predict_array[user_indexx]))\n",
    "        #min(min(predict_quantity,len(predict_item_index_v)))个最前面的商品索引，即打分最高的商品索引\n",
    "        predict_item_index_v=predict_item_index_v[0:min(predict_quantity,len(predict_item_index_v))]\n",
    "        #添加到商品推荐字典中\n",
    "        predict_item_index_map[user_index] = predict_item_index_v\n",
    "    #返回商品推荐字典\n",
    "    return predict_item_index_map\n",
    "#推荐50个商品\n",
    "predict_item_index_map = predict(50)\n",
    "#打印商品推荐字典\n",
    "predict_item_index_map"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#计算准确率和召回率\n",
    "def calculate_precision_and_recall():\n",
    "    #推荐的商品也在测试集中出现的总数\n",
    "    union_num = 0\n",
    "    #推荐的商品的总数\n",
    "    predict_num = 0\n",
    "    #测试集中出现的商品总数\n",
    "    test_num = 0\n",
    "    #对测试集的user_index进行遍历\n",
    "    for user_index in df_user_item_rating_test.index:\n",
    "        #对测试集中的user_index打过分的商品，进行倒排序，得到索引\n",
    "        #（这里其实没有使用到倒序排序功能，比如可以取测试集中倒序的前100个索引test_item_v)\n",
    "        test_item_v = np.where(df_user_item_rating_test[user_index]>=3)[0].tolist()\n",
    "        #推荐的商品也在测试集中出现的总数做叠加\n",
    "        union_num += len(\n",
    "            set(predict_item_index_map[user_index]) & set(test_item_v)\n",
    "        )\n",
    "        #推荐的商品的总数做叠加\n",
    "        predict_num += len(predict_item_index_map[user_index])\n",
    "        #测试集中出现的商品总数做叠加\n",
    "        test_num += len(test_item_v)\n",
    "    #返回准确率与召回率\n",
    "    return union_num / predict_num.union_num/test_num\n",
    "#计算准确率与召回率\n",
    "precision.recall = calculate_precision_and_recall()\n",
    "print('precision=',precision)\n",
    "print('recall=',recall)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义计算覆盖率\n",
    "def calculate_coverage():\n",
    "    #推荐的物品索引集合\n",
    "    predict_item_index_set = set()\n",
    "    #把所有用户推荐过的商品id都添加到predict_item_index_set里，然后根据predict_item_index_set的数量，计算覆盖度\n",
    "    for user_index in predict_item_index_map.keys():\n",
    "        for item_index in predict_item_index_map[user_index]:\n",
    "            predict_item_index_set.add(item_index)\n",
    "    return len(predict_item_index_set) / len(item_id_s)\n",
    "#计算并打印覆盖度\n",
    "coverage = calculate_coverage()\n",
    "print('coverage=',coverage)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
